/* global google */
(function () {
"use strict";
var cfg =
typeof window.travelgoHotelGooglePlacesEmbed === "object" && window.travelgoHotelGooglePlacesEmbed
? window.travelgoHotelGooglePlacesEmbed
: {};
var S = cfg.strings || {};
var pageSize = Math.max(6, Math.min(30, parseInt(String(cfg.pageSize || "12"), 10) || 12));
var maxPages = Math.max(1, Math.min(10, parseInt(String(cfg.maxPages || "6"), 10) || 6));
var resultsPageUrl = typeof cfg.resultsPageUrl === "string" ? cfg.resultsPageUrl : "";
var BOOKING_ENABLED = !!cfg.bookingEnabled;
var BOOKING_AJAX_URL = typeof cfg.bookingAjaxUrl === "string" ? cfg.bookingAjaxUrl : "";
var BOOKING_NONCE = typeof cfg.bookingNonce === "string" ? cfg.bookingNonce : "";
function esc(s) {
var d = document.createElement("div");
d.textContent = s == null ? "" : String(s);
return d.innerHTML;
}
function localIsoDate(d) {
if (!d || isNaN(d.getTime())) {
return "";
}
var y = d.getFullYear();
var m = d.getMonth() + 1;
var day = d.getDate();
return y + "-" + pad2(m) + "-" + pad2(day);
}
function pad2(n) {
return n < 10 ? "0" + n : String(n);
}
function hashSeed(str) {
var h = 0;
var i;
str = String(str || "");
for (i = 0; i < str.length; i++) {
h = (h << 5) - h + str.charCodeAt(i);
h |= 0;
}
return Math.abs(h);
}
function getPriceInrAmount(place) {
var r = place.rating;
if (typeof r !== "number" || !isFinite(r)) {
r = 3.2;
}
var seed = hashSeed(place.place_id || place.name || "");
var pl = place.price_level;
var tier = 0;
if (typeof pl === "number" && isFinite(pl) && pl >= 0 && pl <= 4) {
tier = pl;
}
// Google Places does not return real nightly rates — this is indicative only.
// Old formula floored most listings around ₹2k+; widen range and use price_level when present.
var fromTier = tier * 340;
var fromRating = Math.round(r * 220);
var spread = seed % 2200;
return 399 + fromTier + fromRating + spread;
}
window.travelgoHotelGooglePlacesEmbedInit = function () {
var root =
document.querySelector(".travelgo-gpe-embed[data-travelgo-gpe-root][data-tgpe-page=\"results\"]") ||
document.querySelector(".travelgo-gpe-embed[data-travelgo-gpe-root]");
if (!root || typeof google === "undefined" || !google.maps || !google.maps.places) {
return;
}
var pageMode = root.getAttribute("data-tgpe-page") === "results" ? "results" : "form";
var autocompleteService = new google.maps.places.AutocompleteService();
var mapDiv = root.querySelector("#tgpe-map-hidden");
if (!mapDiv) {
return;
}
var map = new google.maps.Map(mapDiv, { center: { lat: 20, lng: 0 }, zoom: 2 });
var placesService = new google.maps.places.PlacesService(map);
var sessionToken = new google.maps.places.AutocompleteSessionToken();
var allHotels = [];
var seenIds = {};
var lastFiltered = [];
var currentDestLabel = "";
var currentPageCount = 0;
var currentMinRating = 0;
var currentSort = "default";
var currentResultsPage = 0;
var filterNameNeedle = "";
var destInput = root.querySelector("#tgpe-dest-input");
var pacDrop = root.querySelector("#tgpe-pac-drop");
var checkin = root.querySelector("#tgpe-checkin");
var checkout = root.querySelector("#tgpe-checkout");
var searchBtn = root.querySelector("#tgpe-search-btn");
var resultsWrap = root.querySelector("#tgpe-results-wrap");
var filterMinRating = root.querySelector("#tgpe-filter-min-rating");
var filterNameInput = root.querySelector("#tgpe-filter-name");
var filterMaxPriceInput = root.querySelector("#tgpe-filter-max-price");
var filterPropertyType = root.querySelector("#tgpe-filter-property-type");
var currencySelect = root.querySelector("#tgpe-currency-select");
var drawerToggle = root.querySelector("#tgpe-filter-drawer-toggle");
var drawerBackdrop = root.querySelector("#tgpe-mm-drawer-backdrop");
var drawerClose = root.querySelector("#tgpe-mm-drawer-close");
var sidebarEl = root.querySelector("#tgpe-mm-sidebar");
if (!destInput || !placesService || !searchBtn) {
return;
}
if (pageMode === "results" && !resultsWrap) {
return;
}
var currencyList =
Array.isArray(cfg.currencies) && cfg.currencies.length
? cfg.currencies
: [
{ code: "INR", label: "INR \u20B9" },
{ code: "USD", label: "USD $" },
];
var fxRaw = cfg.fxFromInr && typeof cfg.fxFromInr === "object" ? cfg.fxFromInr : {};
function normalizeCur(c) {
var u = String(c || "")
.toUpperCase()
.replace(/[^A-Z]/g, "")
.slice(0, 3);
var i;
for (i = 0; i < currencyList.length; i++) {
var code = String(currencyList[i].code || "")
.toUpperCase()
.replace(/[^A-Z]/g, "")
.slice(0, 3);
if (code && code === u) return code;
}
return "";
}
var displayCurrency =
normalizeCur(cfg.defaultCurrency || cfg.currency || "INR") || "INR";
var searchDone = false;
var stallTimer = null;
function clearStallTimer() {
if (stallTimer) {
clearTimeout(stallTimer);
stallTimer = null;
}
}
function armStallTimer(ms) {
clearStallTimer();
stallTimer = setTimeout(function () {
if (!searchDone) {
finalizeOnce();
}
}, ms);
}
function resetBtn() {
if (searchBtn) {
searchBtn.disabled = false;
searchBtn.textContent = searchBtn.getAttribute("data-tgpe-label") || "SEARCH";
}
}
function setDates() {
var t = new Date();
var ci = new Date(t);
var co;
if (root.classList.contains("travelgo-gpe--akbar")) {
ci.setDate(ci.getDate() + 1);
co = new Date(ci);
co.setDate(co.getDate() + 1);
} else {
ci.setDate(ci.getDate() + 7);
co = new Date(t);
co.setDate(co.getDate() + 10);
}
if (checkin) checkin.value = localIsoDate(ci);
if (checkout) checkout.value = localIsoDate(co);
}
var isAkbarForm = root.classList.contains("travelgo-gpe--akbar");
var roomsCount = 1;
var guestsCount = 1;
var checkinDisplay = root.querySelector("#tgpe-checkin-display");
var checkoutDisplay = root.querySelector("#tgpe-checkout-display");
var checkinDow = root.querySelector("#tgpe-checkin-dow");
var checkoutDow = root.querySelector("#tgpe-checkout-dow");
var nightsNum = root.querySelector("#tgpe-nights-num");
var nightsLbl = root.querySelector("#tgpe-nights-lbl");
var paxSummary = root.querySelector("#tgpe-pax-summary");
var paxTrigger = root.querySelector("#tgpe-pax-trigger");
var paxPop = root.querySelector("#tgpe-pax-pop");
var paxBackdrop = root.querySelector("#tgpe-pax-backdrop");
var paxApply = root.querySelector("#tgpe-pax-apply");
var roomsRow = root.querySelector("#tgpe-rooms-row");
var guestsRow = root.querySelector("#tgpe-guests-row");
function formatAkbarDate(iso) {
if (!iso) {
return { main: "\u2014", dow: "" };
}
var d = new Date(iso + "T12:00:00");
if (isNaN(d.getTime())) {
return { main: "\u2014", dow: "" };
}
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
return {
main: d.getDate() + " " + months[d.getMonth()] + "'" + String(d.getFullYear()).slice(2),
dow: days[d.getDay()],
};
}
function nightsBetween(ci, co) {
if (!ci || !co) {
return 1;
}
var a = new Date(ci + "T12:00:00");
var b = new Date(co + "T12:00:00");
var diff = Math.round((b.getTime() - a.getTime()) / 86400000);
return Math.max(1, diff);
}
function updateAkbarDateDisplays() {
if (!isAkbarForm) {
return;
}
var ciFmt = formatAkbarDate(checkin ? checkin.value : "");
var coFmt = formatAkbarDate(checkout ? checkout.value : "");
if (checkinDisplay) checkinDisplay.textContent = ciFmt.main;
if (checkinDow) checkinDow.textContent = ciFmt.dow;
if (checkoutDisplay) checkoutDisplay.textContent = coFmt.main;
if (checkoutDow) checkoutDow.textContent = coFmt.dow;
var nights = nightsBetween(checkin ? checkin.value : "", checkout ? checkout.value : "");
if (nightsNum) nightsNum.textContent = String(nights);
if (nightsLbl) {
nightsLbl.textContent = nights === 1 ? S.nightSingular || "NIGHT" : S.nightPlural || "NIGHTS";
}
}
function updatePaxSummary() {
if (!paxSummary) {
return;
}
var roomWord = roomsCount === 1 ? S.roomSingular || "Room" : S.roomPlural || "Rooms";
var guestWord = guestsCount === 1 ? S.guestSingular || "Guest" : S.guestPlural || "Guests";
paxSummary.textContent = roomsCount + " " + roomWord + " " + guestsCount + " " + guestWord;
}
function renderAkbarNumRow(container, value, min, max, onChange) {
if (!container) {
return;
}
container.innerHTML =
'− ' +
'' +
esc(value) +
" " +
'+ ';
container.querySelectorAll("[data-delta]").forEach(function (btn) {
btn.addEventListener("click", function () {
var delta = parseInt(btn.getAttribute("data-delta"), 10) || 0;
var next = Math.min(max, Math.max(min, value + delta));
if (next !== value) {
value = next;
onChange(value);
renderAkbarNumRow(container, value, min, max, onChange);
}
});
});
}
function closePaxPop() {
if (paxPop) paxPop.setAttribute("hidden", "hidden");
if (paxBackdrop) paxBackdrop.setAttribute("hidden", "hidden");
if (paxTrigger) paxTrigger.setAttribute("aria-expanded", "false");
}
function openPaxPop() {
if (!paxPop) {
return;
}
var cal = root.querySelector("#tgpe-date-cal");
if (cal && !cal.hasAttribute("hidden") && window.travelgoHotelAkbarCalendarInit) {
var calBackdrop = root.querySelector("#tgpe-date-cal-backdrop");
cal.setAttribute("hidden", "hidden");
if (calBackdrop) {
calBackdrop.setAttribute("hidden", "hidden");
}
}
renderAkbarNumRow(roomsRow, roomsCount, 1, 6, function (v) {
roomsCount = v;
});
renderAkbarNumRow(guestsRow, guestsCount, 1, 12, function (v) {
guestsCount = v;
});
paxPop.removeAttribute("hidden");
if (paxBackdrop) paxBackdrop.removeAttribute("hidden");
if (paxTrigger) paxTrigger.setAttribute("aria-expanded", "true");
}
function wireAkbarHotelForm() {
if (!isAkbarForm) {
return;
}
if (root.getAttribute("data-tgpe-akbar-wired") === "1") {
return;
}
if (typeof window.travelgoHotelAkbarCalendarInit === "function") {
window.travelgoHotelAkbarCalendarInit(root);
}
function onDateChange() {
if (checkin && checkout && checkin.value) {
var co = new Date(checkout.value + "T12:00:00");
var ci = new Date(checkin.value + "T12:00:00");
if (!checkout.value || co <= ci) {
var fix = new Date(ci);
fix.setDate(fix.getDate() + 1);
checkout.value = localIsoDate(fix);
}
}
updateAkbarDateDisplays();
}
root.addEventListener("travelgo:hotel-akbar-dates-updated", onDateChange);
if (paxTrigger) {
paxTrigger.addEventListener("click", function () {
if (paxPop && paxPop.hasAttribute("hidden")) {
openPaxPop();
} else {
closePaxPop();
}
});
}
if (paxBackdrop) {
paxBackdrop.addEventListener("click", closePaxPop);
}
if (paxApply) {
paxApply.addEventListener("click", function () {
updatePaxSummary();
closePaxPop();
});
}
updateAkbarDateDisplays();
updatePaxSummary();
root.setAttribute("data-tgpe-akbar-wired", "1");
}
function readUrlIntoForm() {
try {
var params = new URLSearchParams(window.location.search);
var d = params.get("tgpe_dest");
var ci = params.get("tg_checkin");
var co = params.get("tg_checkout");
var rooms = params.get("tg_rooms");
var guests = params.get("tg_guests");
if (d) destInput.value = d;
if (ci && checkin) checkin.value = ci;
if (co && checkout) checkout.value = co;
if (rooms) roomsCount = Math.max(1, parseInt(String(rooms), 10) || 1);
if (guests) guestsCount = Math.max(1, parseInt(String(guests), 10) || 1);
var curQ = normalizeCur(params.get("tgpe_cur") || "");
if (curQ) {
displayCurrency = curQ;
} else {
try {
var ls = localStorage.getItem("travelgo_gpe_currency");
if (ls && normalizeCur(ls)) displayCurrency = normalizeCur(ls);
} catch (eLs) {}
}
} catch (e0) {}
}
function fxRateForCode(code) {
var c = normalizeCur(code) || "INR";
var v = parseFloat(String(fxRaw[c]));
if (c === "INR") {
return 1;
}
if (isFinite(v) && v > 0) {
return v;
}
return c === "USD" ? 0.012 : 1;
}
function fxActive() {
return fxRateForCode(displayCurrency);
}
function inrToDisplayAmount(inr) {
return Math.round(Number(inr) * fxActive());
}
function displayToInrAmount(amt) {
var fx = fxActive();
if (!fx) {
return Number(amt);
}
return Number(amt) / fx;
}
function formatDisplayFromInr(inr) {
var num = inrToDisplayAmount(inr);
var c = displayCurrency;
if (typeof Intl !== "undefined" && Intl.NumberFormat) {
var loc = c === "INR" ? "en-IN" : "en-US";
try {
return new Intl.NumberFormat(loc, {
style: "currency",
currency: c,
maximumFractionDigits: 0,
}).format(num);
} catch (eIntl) {
/* ignore */
}
}
if (c === "USD") {
return "$" + num.toLocaleString("en-US");
}
return "\u20B9" + num.toLocaleString("en-IN");
}
if (pageMode === "results") {
readUrlIntoForm();
} else {
if (!root.getAttribute("data-tgpe-cal-wired")) {
setDates();
}
if (isAkbarForm) {
updateAkbarDateDisplays();
}
try {
var p0 = new URLSearchParams(window.location.search);
var c0 = normalizeCur(p0.get("tgpe_cur") || "");
if (c0) {
displayCurrency = c0;
} else {
var ls0 = localStorage.getItem("travelgo_gpe_currency");
if (ls0 && normalizeCur(ls0)) {
displayCurrency = normalizeCur(ls0);
}
}
} catch (eLs0) {}
}
function hideDrop() {
if (pacDrop) pacDrop.classList.remove("show");
var hero = document.querySelector(".travelgo-hero--akbar");
if (hero) hero.classList.remove("is-hotel-pac-open");
}
var indianCityIata = {
kolkata: "CCU",
mumbai: "BOM",
delhi: "DEL",
"new delhi": "DEL",
bangalore: "BLR",
bengaluru: "BLR",
hyderabad: "HYD",
chennai: "MAA",
goa: "GOI",
kochi: "COK",
cochin: "COK",
jaipur: "JAI",
lucknow: "LKO",
ahmedabad: "AMD",
pune: "PNQ",
srinagar: "SXR",
guwahati: "GAU",
varanasi: "VNS",
patna: "PAT",
indore: "IDR",
nagpur: "NAG",
kolar: "KQZ",
};
function iataForPlace(main, sec) {
var key = String(main || "").toLowerCase().trim();
if (indianCityIata[key]) {
return indianCityIata[key];
}
if (sec && /india/i.test(sec)) {
return "";
}
return "";
}
function rankPrediction(p) {
var main = (p.structured_formatting && p.structured_formatting.main_text) || "";
var sec = (p.structured_formatting && p.structured_formatting.secondary_text) || "";
var score = 0;
var mainKey = main.toLowerCase().trim();
if (/india/i.test(sec)) {
score += 120;
}
if (indianCityIata[mainKey]) {
score += 80;
}
if (/,\s*india$/i.test(sec)) {
score += 40;
}
return score;
}
function showDrop(preds) {
if (!pacDrop) return;
var sorted = preds.slice().sort(function (a, b) {
return rankPrediction(b) - rankPrediction(a);
});
pacDrop.innerHTML = sorted
.map(function (p) {
var main = p.structured_formatting && p.structured_formatting.main_text ? p.structured_formatting.main_text : "";
var sec =
p.structured_formatting && p.structured_formatting.secondary_text
? p.structured_formatting.secondary_text
: "";
var desc = p.description || "";
var code = iataForPlace(main, sec);
return (
'' +
'📍 ' +
"" +
esc(main) +
(code ? '' + esc(code) + " " : "") +
" " +
(sec ? '' + esc(sec) + " " : "") +
" "
);
})
.join("");
pacDrop.classList.add("show");
var hero = document.querySelector(".travelgo-hero--akbar");
if (hero) hero.classList.add("is-hotel-pac-open");
}
function onInput() {
var val = destInput.value;
if (!val || val.length < 2) {
hideDrop();
return;
}
var req = {
input: val,
sessionToken: sessionToken,
};
var countryBias =
cfg.hotelAutocompleteCountry !== undefined && cfg.hotelAutocompleteCountry !== null
? String(cfg.hotelAutocompleteCountry).trim()
: "in";
if (countryBias) {
req.componentRestrictions = { country: countryBias };
}
autocompleteService.getPlacePredictions(req, function (preds, status) {
if (status !== google.maps.places.PlacesServiceStatus.OK || !preds || !preds.length) {
hideDrop();
return;
}
showDrop(preds.slice(0, 8));
});
}
destInput.addEventListener("input", onInput);
destInput.addEventListener("blur", function () {
setTimeout(hideDrop, 180);
});
destInput.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
e.preventDefault();
doSearch();
}
});
if (pacDrop) {
var lastPacPickAt = 0;
function pickPacItem(e) {
if (!e || !e.target || !e.target.closest) return;
var btn = e.target.closest(".tgpe-pac-item");
if (!btn || !pacDrop.contains(btn)) return;
var now = Date.now();
if (now - lastPacPickAt < 350) return;
lastPacPickAt = now;
if (e.cancelable) e.preventDefault();
var d = btn.getAttribute("data-tgpe-desc");
if (d) {
try {
destInput.value = decodeURIComponent(d);
} catch (e2) {
destInput.value = d;
}
hideDrop();
}
}
if (typeof window.PointerEvent !== "undefined") {
pacDrop.addEventListener("pointerdown", pickPacItem, true);
} else {
pacDrop.addEventListener("mousedown", pickPacItem, true);
pacDrop.addEventListener(
"touchend",
function (e) {
pickPacItem(e);
},
{ capture: true, passive: false }
);
}
}
function goToResultsPage() {
var dest = destInput.value.trim();
if (!dest) {
destInput.focus();
return;
}
if (!resultsPageUrl) {
window.alert(S.configureResults || "Configure a hotel results page in the theme.");
return;
}
var params = new URLSearchParams();
params.set("tgpe_dest", dest);
if (checkin && checkin.value) params.set("tg_checkin", checkin.value);
if (checkout && checkout.value) params.set("tg_checkout", checkout.value);
params.set("tg_rooms", String(roomsCount));
params.set("tg_guests", String(guestsCount));
params.set("tgpe_cur", displayCurrency);
var sep = resultsPageUrl.indexOf("?") >= 0 ? "&" : "?";
window.location.href = resultsPageUrl + sep + params.toString();
}
function syncResultsUrl() {
if (pageMode !== "results") return;
try {
var params = new URLSearchParams(window.location.search);
params.set("tgpe_dest", destInput.value.trim());
if (checkin && checkin.value) params.set("tg_checkin", checkin.value);
if (checkout && checkout.value) params.set("tg_checkout", checkout.value);
params.set("tg_rooms", String(roomsCount));
params.set("tg_guests", String(guestsCount));
params.set("tgpe_cur", displayCurrency);
var q = params.toString();
if (window.history && window.history.replaceState) {
window.history.replaceState({}, "", window.location.pathname + (q ? "?" + q : ""));
}
} catch (e1) {}
}
function mergeResults(results) {
if (!results) return;
var i;
for (i = 0; i < results.length; i++) {
var p = results[i];
if (!p || !p.place_id) continue;
if (seenIds[p.place_id]) continue;
seenIds[p.place_id] = true;
allHotels.push(p);
}
}
function buildStaysQuery(dest) {
return (
"hotels homestays guest houses lodges hostels serviced apartments budget stays in " + dest
);
}
/** Second-phase query so the list also pulls low-cost segments Places ranks separately. */
function buildBudgetStaysQuery(dest) {
return (
"cheap budget affordable economy low cost backpacker hostels motels dormitory PG paying guest capsule lodge rooms in " +
dest
);
}
function finalizeOnce() {
if (searchDone) {
return;
}
searchDone = true;
clearStallTimer();
resetBtn();
if (pageMode === "results") {
syncResultsUrl();
renderResultsChrome();
applyFiltersAndSort();
}
}
function doSearch() {
if (pageMode === "form") {
goToResultsPage();
return;
}
var dest = destInput.value.trim();
if (!dest) {
destInput.focus();
return;
}
currentDestLabel = dest;
allHotels = [];
seenIds = {};
lastFiltered = [];
currentPageCount = 0;
currentResultsPage = 0;
searchDone = false;
clearStallTimer();
armStallTimer(32000);
if (searchBtn) {
searchBtn.disabled = true;
searchBtn.textContent = S.searching || "Searching…";
}
if (resultsWrap) {
resultsWrap.style.display = "block";
resultsWrap.innerHTML =
'
' +
'
' +
'
' +
esc(S.searchingIn || "Searching stays in") +
' ' +
esc(dest.split(",")[0]) +
" …
" +
'
' +
'
' +
esc(S.fetchingPages || "Fetching listings…") +
"
";
}
var compositePhase = 0;
var pagesThisPhase = 0;
var phases = [
{ query: buildStaysQuery(dest), pageCap: maxPages },
{
query: buildBudgetStaysQuery(dest),
pageCap: Math.max(2, Math.min(4, maxPages)),
},
];
function showNoResultsEmpty() {
searchDone = true;
clearStallTimer();
if (resultsWrap) {
resultsWrap.innerHTML =
'' +
'
' +
esc(S.noneFound || "No stays found") +
"
" +
'
"' +
esc(currentDestLabel) +
"". " +
esc(S.tryOther || "Try another search.") +
"
";
}
resetBtn();
}
function goNextPhaseOrFinish() {
if (compositePhase + 1 < phases.length) {
compositePhase++;
pagesThisPhase = 0;
kickTextSearchPhase();
return;
}
finalizeOnce();
}
function kickTextSearchPhase() {
var ph = phases[compositePhase];
if (!ph) {
finalizeOnce();
return;
}
try {
placesService.textSearch({ query: ph.query }, onTextResults);
} catch (eKick) {
if (compositePhase + 1 < phases.length) {
goNextPhaseOrFinish();
} else {
if (allHotels.length > 0) {
finalizeOnce();
} else {
showNoResultsEmpty();
}
}
}
}
function onTextResults(results, status, pagination) {
if (searchDone) {
return;
}
var prog = resultsWrap ? resultsWrap.querySelector("#tgpe-prog") : null;
var pageStatus = resultsWrap ? resultsWrap.querySelector("#tgpe-page-status") : null;
var ph = phases[compositePhase];
var cap = ph ? ph.pageCap : maxPages;
if (status === google.maps.places.PlacesServiceStatus.OK && results && results.length) {
mergeResults(results);
currentPageCount++;
pagesThisPhase++;
if (prog) prog.style.width = Math.min(92, 10 + currentPageCount * 12) + "%";
if (pageStatus) {
var tail =
pagination && pagination.hasNextPage && pagesThisPhase < cap
? "…"
: compositePhase + 1 < phases.length
? " · " + (S.fetchingBudgetStays || "Adding budget stays…")
: "";
pageStatus.textContent =
(S.foundSoFar || "Found") +
" " +
allHotels.length +
" " +
(S.properties || "properties") +
tail;
}
if (pagination && pagination.hasNextPage && pagesThisPhase < cap) {
armStallTimer(16000);
setTimeout(function () {
if (searchDone) {
return;
}
try {
pagination.nextPage();
} catch (e3) {
goNextPhaseOrFinish();
}
}, 1600);
} else {
goNextPhaseOrFinish();
}
} else {
if (compositePhase + 1 < phases.length) {
goNextPhaseOrFinish();
} else if (allHotels.length > 0) {
finalizeOnce();
} else {
showNoResultsEmpty();
}
}
}
try {
kickTextSearchPhase();
} catch (eTs) {
if (!searchDone && compositePhase + 1 < phases.length) {
goNextPhaseOrFinish();
} else if (!searchDone && allHotels.length > 0) {
finalizeOnce();
} else if (!searchDone) {
searchDone = true;
clearStallTimer();
resetBtn();
if (resultsWrap) {
resultsWrap.innerHTML =
'' +
esc(S.noneFound || "No stays found") +
"
" +
esc(S.tryOther || "Try again or refresh the page.") +
"
";
}
}
}
}
if (searchBtn) {
searchBtn.addEventListener("click", doSearch);
}
function getPhotoUrl(place, maxW) {
maxW = maxW || 500;
if (place.photos && place.photos.length > 0) {
return place.photos[0].getUrl({ maxWidth: maxW, maxHeight: 280 });
}
return null;
}
function renderStars(r) {
if (!r) return "\u2013";
var f = Math.floor(r);
var h = r % 1 >= 0.4;
var s = "";
var i;
for (i = 0; i < f; i++) s += "\u2605";
if (h) s += "\u00bd";
return s;
}
function classifyProperty(h) {
var name = String((h && h.name) || "").toLowerCase();
var types = Array.isArray(h.types) ? h.types : [];
if (
/\b(homestay|guest\s*house|guesthouse|hostel|dormitory|dorm\b|pg\b|paying guest|bnb|bed\s*&?\s*breakfast)\b/i.test(
name
)
) {
return "homestay";
}
var lodgingish = types.some(function (t) {
return (
t === "lodging" ||
t === "resort_hotel" ||
t === "motel" ||
t === "campground" ||
t === "rv_park"
);
});
if (lodgingish) {
return "hotel";
}
if (
/\b(hotel|resort|suites|palace|inn|tower|plaza|club|international)\b/i.test(name)
) {
return "hotel";
}
if (
/\b(lodge|hostel|motel|service apartment)\b/i.test(name)
) {
return "homestay";
}
return "other";
}
function rowBadgesHtml(h) {
var r = h.rating || 0;
var cnt = h.user_ratings_total || 0;
var amt = getPriceInrAmount(h);
var seed = hashSeed(h.place_id || h.name || "");
var parts = [];
if (r >= 4.2) {
parts.push('' + esc(S.badgeTopRated || "Top rated") + " ");
}
if (cnt >= 45 && seed % 4 !== 0) {
parts.push('' + esc(S.badgePopular || "Popular") + " ");
}
if (amt <= 1650 && r >= 3.5) {
parts.push('' + esc(S.badgeValue || "Great value") + " ");
}
return parts.length ? '' + parts.join("") + "
" : "";
}
function mapsPlaceUrl(pid) {
return "https://www.google.com/maps/place/?q=place_id:" + encodeURIComponent(pid);
}
function findHotelByPlaceId(pid) {
if (!pid) return null;
var j;
for (j = 0; j < allHotels.length; j++) {
if (allHotels[j] && allHotels[j].place_id === pid) return allHotels[j];
}
return null;
}
function buildSearchBookingObject() {
return {
q: currentDestLabel,
check_in_date: checkin ? checkin.value : "",
check_out_date: checkout ? checkout.value : "",
adults: 2,
children: 0,
currency: displayCurrency,
};
}
function buildHotelBookingObject(h) {
var u = getPhotoUrl(h, 800);
var pid = h.place_id || "";
return {
name: h.name || "Stay",
type: "Google Places",
property_token: "tg_gplace_" + pid,
google_place_id: pid,
link: mapsPlaceUrl(pid),
thumbnail: u || "",
overall_rating: h.rating != null ? h.rating : "",
reviews: h.user_ratings_total || 0,
price: inrToDisplayAmount(getPriceInrAmount(h)),
};
}
function startBooking(h, btn) {
if (!BOOKING_ENABLED || !BOOKING_AJAX_URL || !BOOKING_NONCE) {
window.alert(S.bookingNotConfigured || "Hotel booking is not configured.");
return;
}
var payload = {
search: buildSearchBookingObject(),
hotel: buildHotelBookingObject(h),
};
btn.disabled = true;
var fd = new FormData();
fd.append("action", "travelgo_save_hotel_booking");
fd.append("nonce", BOOKING_NONCE);
fd.append("payload", JSON.stringify(payload));
fetch(BOOKING_AJAX_URL, { method: "POST", body: fd, credentials: "same-origin" })
.then(function (r) {
return r.json();
})
.then(function (res) {
btn.disabled = false;
if (!res || !res.success || !res.data || !res.data.redirect) {
window.alert((res && res.data && res.data.message) || S.bookingSaveError || "Could not start booking.");
return;
}
window.location.href = res.data.redirect;
})
.catch(function () {
btn.disabled = false;
window.alert(S.bookingSaveError || "Could not start booking.");
});
}
function hotelRow(h) {
var photoUrl = getPhotoUrl(h);
var imgHtml = photoUrl
? ' '
: '🏨
';
var r = h.rating || 0;
var cnt = h.user_ratings_total || 0;
var amt = getPriceInrAmount(h);
var price = formatDisplayFromInr(amt);
var openHtml = h.opening_hours
? '' +
(h.opening_hours.open_now ? S.openNow || "Open" : S.closed || "Closed") +
" "
: "";
var addr = (h.formatted_address || h.vicinity || "").split(",").slice(0, 3).join(",");
var rid = (h.place_id || "").replace(/[^a-z0-9]/gi, "");
var reviewsHtml = (h.reviews || [])
.slice(0, 1)
.filter(function (rv) {
return rv.text && rv.text.length > 10;
})
.map(function (rv) {
var t = rv.text.slice(0, 140) + (rv.text.length > 140 ? "\u2026" : "");
return '' + esc(t) + "
";
})
.join("");
var bookBtn =
BOOKING_ENABLED && h.place_id
? '' +
esc(S.bookNow || "Book now") +
" "
: "";
var mapLink =
'' +
esc(S.viewOnMap || "View on map") +
" ";
return (
'' +
'' +
imgHtml +
"
" +
'' +
'
' +
esc(h.name) +
" " +
rowBadgesHtml(h) +
'
📍 ' +
esc(addr) +
" " +
openHtml +
"
" +
(r
? '
' +
renderStars(r) +
' ' +
r.toFixed(1) +
' (' +
cnt.toLocaleString("en-IN") +
")
"
: "") +
'
' +
(reviewsHtml || "") +
"
" +
'' +
'
' +
esc(S.from || "from") +
" " +
'
' +
esc(price) +
"
" +
'
' +
esc(S.perNight || "per night") +
' ' +
bookBtn +
mapLink +
"
"
);
}
function renderResultsChrome() {
if (!resultsWrap) return;
var ci = checkin ? checkin.value : "";
var co = checkout ? checkout.value : "";
var nights =
ci && co ? Math.max(1, Math.round((new Date(co) - new Date(ci)) / 86400000)) : 3;
var city = currentDestLabel.split(",")[0];
resultsWrap.innerHTML =
'' +
'
' +
'
' +
esc(S.hotelsIn || "Stays in") +
" " +
esc(city) +
" " +
'
' +
esc(String(allHotels.length)) +
" " +
esc(S.properties || "listings") +
" \u00b7 " +
esc(String(nights)) +
" " +
(nights > 1 ? esc(S.nights || "nights") : esc(S.night || "night")) +
"
" +
'
' +
'' +
'' +
esc(S.sort || "Sort") +
": " +
'' +
esc(S.sortBest || "Best match") +
" " +
'' +
esc(S.sortRated || "Top rated") +
" " +
'' +
esc(S.sortReviews || "Most reviewed") +
" " +
'' +
esc(S.sortPriceUp || "Price \u2191") +
" " +
'' +
esc(S.sortPriceDown || "Price \u2193") +
"
" +
'
' +
'' +
'' +
esc(
S.showingFromGoogle && S.showingFromGoogle.indexOf("%s") >= 0
? S.showingFromGoogle.replace(/%s/g, displayCurrency)
: S.showingFromGoogle ||
"Listings from Google Places · indicative nightly prices (" + displayCurrency + ")"
) +
"
";
wireSortFilter();
updateCurrencyDependentUi();
}
function wireHotelImageFallbacks(container) {
(container || resultsWrap).querySelectorAll("img.tgpe-hotel-img").forEach(function (img) {
img.addEventListener(
"error",
function ph() {
img.removeEventListener("error", ph);
var d = document.createElement("div");
d.className = "tgpe-hotel-img-ph tgpe-hotel-row__img";
d.setAttribute("role", "img");
d.textContent = String.fromCodePoint(0x1f3e8);
img.replaceWith(d);
},
{ once: true }
);
});
}
function wireCardClicks() {
if (!resultsWrap) return;
resultsWrap.querySelectorAll(".tgpe-hotel-row").forEach(function (card) {
card.addEventListener("click", function (ev) {
if (ev.target.closest("[data-tgpe-stop-row]")) return;
var pid = card.getAttribute("data-tgpe-place-id");
if (!pid) return;
var url = mapsPlaceUrl(pid);
try {
window.open(url, "_blank", "noopener,noreferrer");
} catch (e2) {
window.location.href = url;
}
});
});
}
function wireBookButtons() {
if (!resultsWrap) return;
resultsWrap.querySelectorAll(".tgpe-hotel-book").forEach(function (btn) {
btn.addEventListener("click", function (ev) {
ev.preventDefault();
ev.stopPropagation();
var pid = btn.getAttribute("data-tgpe-place-id");
var h = findHotelByPlaceId(pid);
if (!h) return;
startBooking(h, btn);
});
});
}
function wireSortFilter() {
if (!resultsWrap) return;
resultsWrap.querySelectorAll("[data-tgpe-sort]").forEach(function (btn) {
btn.addEventListener("click", function () {
currentSort = btn.getAttribute("data-tgpe-sort") || "default";
resultsWrap.querySelectorAll(".tgpe-sort-btn").forEach(function (b) {
b.classList.remove("active");
});
btn.classList.add("active");
currentResultsPage = 0;
applyFiltersAndSort();
});
});
}
function wirePagerDelegation() {
if (!resultsWrap || resultsWrap._tgpePagerBound) return;
resultsWrap._tgpePagerBound = true;
resultsWrap.addEventListener("click", function (ev) {
var t = ev.target.closest("[data-tgpe-page-go]");
if (!t || !resultsWrap.contains(t)) return;
ev.preventDefault();
var go = t.getAttribute("data-tgpe-page-go");
if (go === "prev") {
currentResultsPage = Math.max(0, currentResultsPage - 1);
} else if (go === "next") {
var pages = Math.max(1, Math.ceil(lastFiltered.length / pageSize));
currentResultsPage = Math.min(pages - 1, currentResultsPage + 1);
} else {
var n = parseInt(go, 10);
if (!isNaN(n)) currentResultsPage = n;
}
renderListPage();
});
}
function renderListPage() {
if (!resultsWrap) return;
var list = resultsWrap.querySelector("#tgpe-hotel-list");
var pager = resultsWrap.querySelector("#tgpe-pager");
if (!list || !pager) return;
var total = lastFiltered.length;
var pages = Math.max(1, Math.ceil(total / pageSize) || 1);
if (currentResultsPage >= pages) currentResultsPage = pages - 1;
if (currentResultsPage < 0) currentResultsPage = 0;
var start = currentResultsPage * pageSize;
var slice = lastFiltered.slice(start, start + pageSize);
list.innerHTML = slice.length
? slice.map(hotelRow).join("")
: '' + esc(S.noMatchFilter || "No match.") + "
";
var end = start + slice.length;
var ps = S.pagerShowing || "Showing";
var po = S.pagerOf || "of";
var pprev = S.pagerPrev || "Previous";
var pnext = S.pagerNext || "Next";
var ppage = S.pagerPage || "Page";
pager.innerHTML =
'";
wireCardClicks();
wireBookButtons();
wireHotelImageFallbacks(list);
wirePagerDelegation();
fetchReviewsBatch(slice.slice(0, 28));
}
function applyFiltersAndSort() {
if (pageMode !== "results" || !resultsWrap) return;
if (filterMinRating) {
currentMinRating = parseFloat(filterMinRating.value || "0") || 0;
}
var n = filterNameNeedle.trim().toLowerCase();
var maxP = filterMaxPriceInput ? parseFloat(String(filterMaxPriceInput.value || "")) : NaN;
var propType = filterPropertyType ? String(filterPropertyType.value || "all") : "all";
var filtered = allHotels.filter(function (h) {
if ((h.rating || 0) < currentMinRating) return false;
if (n && (h.name || "").toLowerCase().indexOf(n) === -1) return false;
if (!isNaN(maxP) && maxP > 0 && getPriceInrAmount(h) > displayToInrAmount(maxP)) return false;
var cls = classifyProperty(h);
if (propType === "hotel" && cls === "homestay") return false;
if (propType === "homestay" && cls === "hotel") return false;
return true;
});
if (currentSort === "rating") {
filtered.sort(function (a, b) {
return (b.rating || 0) - (a.rating || 0);
});
} else if (currentSort === "reviews") {
filtered.sort(function (a, b) {
return (b.user_ratings_total || 0) - (a.user_ratings_total || 0);
});
} else if (currentSort === "price_asc") {
filtered.sort(function (a, b) {
return getPriceInrAmount(a) - getPriceInrAmount(b);
});
} else if (currentSort === "price_desc") {
filtered.sort(function (a, b) {
return getPriceInrAmount(b) - getPriceInrAmount(a);
});
}
lastFiltered = filtered;
var badge = resultsWrap.querySelector("#tgpe-count-badge");
if (badge) {
var ci = checkin ? checkin.value : "";
var co = checkout ? checkout.value : "";
var nights =
ci && co ? Math.max(1, Math.round((new Date(co) - new Date(ci)) / 86400000)) : 3;
badge.textContent =
filtered.length +
" " +
(S.propertiesNights || "stays") +
" \u00b7 " +
nights +
" " +
(nights > 1 ? S.nights || "nights" : S.night || "night");
}
renderListPage();
}
function fetchReviewsBatch(hotels) {
hotels.forEach(function (hotel, i) {
if (!hotel || !hotel.place_id || hotel._detailsFetched) return;
hotel._detailsFetched = true;
setTimeout(function () {
placesService.getDetails(
{ placeId: hotel.place_id, fields: ["reviews", "opening_hours"] },
function (detail, status) {
if (status === google.maps.places.PlacesServiceStatus.OK && detail) {
var idx = allHotels.findIndex(function (h) {
return h.place_id === hotel.place_id;
});
if (idx > -1) {
allHotels[idx].reviews = detail.reviews || [];
allHotels[idx].opening_hours = detail.opening_hours;
}
if (detail.reviews && detail.reviews.length) {
var rid = hotel.place_id.replace(/[^a-z0-9]/gi, "");
var el = resultsWrap ? resultsWrap.querySelector("#tgpe-rv-" + rid) : null;
if (el) {
var html = detail.reviews
.slice(0, 1)
.filter(function (r) {
return r.text && r.text.length > 10;
})
.map(function (r) {
var t = r.text.slice(0, 140) + (r.text.length > 140 ? "\u2026" : "");
return '' + esc(t) + "
";
})
.join("");
if (html) el.innerHTML = html;
}
}
}
}
);
}, i * 100);
});
}
function resetHotelFiltersDom() {
filterNameNeedle = "";
if (filterNameInput) filterNameInput.value = "";
if (filterMinRating) filterMinRating.value = "0";
if (filterMaxPriceInput) filterMaxPriceInput.value = "";
if (filterPropertyType) filterPropertyType.value = "all";
currentMinRating = 0;
currentSort = "default";
currentResultsPage = 0;
if (resultsWrap) {
resultsWrap.querySelectorAll(".tgpe-sort-btn").forEach(function (b, idx) {
b.classList.toggle("active", idx === 0);
});
}
}
if (sidebarEl) {
sidebarEl.addEventListener("input", function (ev) {
var tid = ev.target && ev.target.id;
if (tid === "tgpe-filter-name") {
clearTimeout(sidebarEl._tgpeNameDeb);
sidebarEl._tgpeNameDeb = setTimeout(function () {
filterNameNeedle = filterNameInput ? filterNameInput.value || "" : "";
currentResultsPage = 0;
applyFiltersAndSort();
}, 220);
return;
}
if (tid === "tgpe-filter-max-price") {
clearTimeout(sidebarEl._tgpePriceDeb);
sidebarEl._tgpePriceDeb = setTimeout(function () {
currentResultsPage = 0;
applyFiltersAndSort();
}, 280);
}
});
sidebarEl.addEventListener("change", function (ev) {
var tid = ev.target && ev.target.id;
if (
tid === "tgpe-filter-min-rating" ||
tid === "tgpe-filter-property-type" ||
tid === "tgpe-filter-max-price"
) {
currentResultsPage = 0;
applyFiltersAndSort();
}
});
sidebarEl.addEventListener("click", function (ev) {
var btn = ev.target && ev.target.closest("#tgpe-filter-reset");
if (!btn || !sidebarEl.contains(btn)) return;
resetHotelFiltersDom();
applyFiltersAndSort();
closeFilterDrawer();
});
}
function setFilterDrawer(open) {
if (!sidebarEl) return;
sidebarEl.classList.toggle("is-open", !!open);
if (drawerBackdrop) {
if (open) drawerBackdrop.removeAttribute("hidden");
else drawerBackdrop.setAttribute("hidden", "");
}
if (drawerToggle) drawerToggle.setAttribute("aria-expanded", open ? "true" : "false");
if (drawerClose) drawerClose.hidden = !open;
if (open) document.body.classList.add("tgpe-mm-drawer-open");
else document.body.classList.remove("tgpe-mm-drawer-open");
}
function closeFilterDrawer() {
setFilterDrawer(false);
}
function wireFilterDrawer() {
if (!drawerToggle || !sidebarEl) return;
drawerToggle.addEventListener("click", function () {
var open = !sidebarEl.classList.contains("is-open");
setFilterDrawer(open);
});
if (drawerBackdrop) {
drawerBackdrop.addEventListener("click", closeFilterDrawer);
}
if (drawerClose) {
drawerClose.addEventListener("click", closeFilterDrawer);
}
}
function persistCurrencySelection() {
try {
localStorage.setItem("travelgo_gpe_currency", displayCurrency);
} catch (e1) {}
}
function populateCurrencySelect(el) {
if (!el) return;
el.innerHTML = currencyList
.map(function (x) {
var code = normalizeCur(x.code);
var lab = x.label != null ? String(x.label) : code;
return '' + esc(lab) + " ";
})
.join("");
el.value = displayCurrency;
}
function updateCurrencyDependentUi() {
var maxLab = root.querySelector('label[for="tgpe-filter-max-price"]');
if (maxLab) {
maxLab.textContent =
(S.maxPricePerNight || "Max price / night") + " (" + displayCurrency + ")";
}
var hint = root.querySelector(".tgpe-mm-sidebar .tgpe-mm-hint");
if (hint) {
hint.textContent =
S.priceIndicativeHint && S.priceIndicativeHint.indexOf("%s") >= 0
? S.priceIndicativeHint.replace(/%s/g, displayCurrency)
: S.priceIndicativeHint ||
"Indicative nightly prices in " +
displayCurrency +
". Confirm on the booking page.";
}
var ribSym = root.querySelector("#tgpe-form-currency-symbol");
var ribTxt = root.querySelector("#tgpe-form-currency-copy");
if (ribSym) {
ribSym.textContent = displayCurrency === "USD" ? "$" : "\u20B9";
}
if (ribTxt) {
ribTxt.textContent =
displayCurrency === "USD"
? S.formRibbonUsd ||
"Indicative nightly prices in US dollars (estimate). Final price on booking."
: S.formRibbonInr ||
"Indicative nightly prices in Indian rupees (estimate). Final price on booking.";
}
if (filterMaxPriceInput) {
filterMaxPriceInput.setAttribute("step", displayCurrency === "USD" ? "1" : "50");
}
var pageInfo = resultsWrap ? resultsWrap.querySelector(".tgpe-page-info") : null;
if (pageInfo) {
pageInfo.textContent =
S.showingFromGoogle && S.showingFromGoogle.indexOf("%s") >= 0
? S.showingFromGoogle.replace(/%s/g, displayCurrency)
: S.showingFromGoogle ||
"Listings from Google Places · indicative nightly prices (" + displayCurrency + ")";
}
}
populateCurrencySelect(currencySelect);
updateCurrencyDependentUi();
if (pageMode === "form" && isAkbarForm) {
wireAkbarHotelForm();
}
if (pageMode === "results" && isAkbarForm) {
updateAkbarDateDisplays();
updatePaxSummary();
}
if (currencySelect) {
currencySelect.addEventListener("change", function () {
var v = normalizeCur(currencySelect.value);
if (!v) return;
displayCurrency = v;
persistCurrencySelection();
currencySelect.value = displayCurrency;
updateCurrencyDependentUi();
if (pageMode === "results" && resultsWrap && allHotels.length) {
syncResultsUrl();
applyFiltersAndSort();
}
});
}
if (pageMode === "results") {
wirePagerDelegation();
wireFilterDrawer();
var autoDest = destInput.value.trim();
if (autoDest) {
currentDestLabel = autoDest;
// Maps invokes this callback before the map/Places stack is always ready; an immediate
// textSearch() can throw or no-op until the first idle tick. Refresh often "fixes" it by
// changing load timing — wait for idle (with a fallback) so the first load is reliable.
var autoSearchKicked = false;
function kickAutoSearchOnce() {
if (autoSearchKicked) return;
autoSearchKicked = true;
doSearch();
}
window.setTimeout(function () {
kickAutoSearchOnce();
}, 1400);
try {
if (
google.maps &&
google.maps.event &&
typeof google.maps.event.addListenerOnce === "function"
) {
google.maps.event.addListenerOnce(map, "idle", function () {
window.setTimeout(kickAutoSearchOnce, 80);
});
} else {
window.setTimeout(kickAutoSearchOnce, 200);
}
} catch (eKick) {
window.setTimeout(kickAutoSearchOnce, 200);
}
}
}
};
})();